/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using VSLangProj;
using VSLangProj80;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	public static class ProjectFactory
	{
		#region Public Methods
		public static IAnyProject CreateProject(object project)
		{
			return CreateProject(project, true);
		}

		/// <param name="canUseCache">Indicates whether the static cache may be searched for <paramref name="project"/> if it's a valid DocProject.
		/// Note that the cache isn't used for other project types (this parameter will be ignored in all other cases).</param>
		public static IAnyProject CreateProject(object project, bool canUseCache)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			Project projectBase = project as Project;

		TryAgain:
			if (projectBase != null)
			{
				IAnyProject any = CreateVSProject(projectBase, canUseCache);

				if (any != null)
					return any;
			}
			else
			{
				VSProject2 vsProject = project as VSProject2;

				if (vsProject != null)
				{
					projectBase = vsProject.Project;
					goto TryAgain;
				}
			}

			throw new ArgumentException(Errors.InvalidProjectArgument, "project");
		}

		public static ISourceProject CreateSourceProject(object project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			Project projectBase = project as Project;

			if (projectBase != null)
			{
				VCSourceProject vcProject = null;

				try
				{
					vcProject = VCProjectFactory.CreateVCSourceProject(projectBase);
				}
				// In case VC++ is not installed 
				catch (System.IO.FileNotFoundException) { }

				if (vcProject != null)
					return vcProject;

				ISourceProject source = CreateVSSourceProject(projectBase);

				if (source != null)
					return source;
			}

			throw new ArgumentException(Errors.InvalidProjectArgument, "project");
		}

		public static bool IsProject(object project)
		{
			// NOTE: allow null project
			if (project == null)
				return false;

			Project projectBase = project as Project;

			if (projectBase != null && IsVSProject(projectBase))
				return true;

			return false;
		}

		public static bool IsDocProject(object project)
		{
			// NOTE: allow null project
			if (project == null)
				return false;

			Globals globals = CreateGlobals(project);

			return globals != null && IsDocProject(globals);
		}

		/// <remarks>
		/// Changes are not committed until the specified <paramref name="project" /> is saved.
		/// </remarks>
		public static void MakeDocProject(object project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			Globals globals = CreateGlobals(project);

			if (globals == null)
				throw new ArgumentException(Errors.InvalidProjectNotDocProject, "project");

			if (!globals.get_VariableExists("IsDocProject")
				|| !((string) globals["IsDocProject"]).Equals("true", StringComparison.OrdinalIgnoreCase))
				globals["IsDocProject"] = "true";
		}

		public static bool IsSourceProject(object project)
		{
			return IsSourceProject(project, true);
		}

		public static bool IsSourceProject(object project, bool canUseCache)
		{
			// NOTE: allow null project
			return IsProject(project) && IsSourceProject(CreateProject(project, canUseCache));
		}

		public static bool IsSourceProject(IAnyProject project)
		{
			// NOTE: allow null project
			return project != null && project.Output.OutputFile.IsAssembly;
		}
		#endregion

		#region Visual Studio Projects
		private static bool IsVSProject(Project project)
		{
			try
			{
				return project.Object is VSProject2;
			}
			catch (NotImplementedException)
			// Thrown during testing on VS 2005 Pro, Windows XP SP2 when the Add-In is initialized
			// if Visual Studio was opened with something like: devenv "somefile.exe" (although it
			// doesn't seem to occur if the argument specifies a text file or a shortcut, for example.)
			{
				return false;
			}
		}

		/// <param name="canUseCache">Indicates whether the static cache may be searched for <paramref name="project"/> if it's a valid DocProject.
		/// Note that the cache isn't used for other project types (this parameter will be ignored in all other cases).</param>
		private static VSAnyProject CreateVSProject(Project project, bool canUseCache)
		{
			VSProject2 vsProject2 = project.Object as VSProject2;

			if (vsProject2 == null)
				return null;		// do not throw exception
			else if (IsDocProject(project.Globals))
			{
				VCDocProject vcProject = null;

				try
				{
					vcProject = VCProjectFactory.CreateVCDocProject(vsProject2, canUseCache);
				}
				// In case VC++ is not installed
				catch (System.IO.FileNotFoundException) { }

				if (vcProject != null)
					return vcProject;
				else if (canUseCache)
				{
					VSDocProject cachedProject = DocProjectEnvironment.GetDocProject(vsProject2) as VSDocProject;

					if (cachedProject != null)
						return cachedProject;
				}
				
				return new VSDocProject(vsProject2);
			}
			else
			{
				VCAnyProject vcProject = null;

				try
				{
					vcProject = VCProjectFactory.CreateVCAnyProject(vsProject2);
				}
				// In case VC++ is not installed
				catch (System.IO.FileNotFoundException) { }

				if (vcProject != null)
					return vcProject;
				else
					return new VSAnyProject(vsProject2);
			}
		}

		private static VSSourceProject CreateVSSourceProject(Project project)
		{
			VSProject2 vsProject2 = project.Object as VSProject2;

			if (vsProject2 == null)
				return null;		// do not throw exception

			VSSourceProject source = new VSSourceProject(vsProject2);

			if (!IsSourceProject(source))
				throw new ArgumentException(Errors.InvalidProjectNoAssemblyOutput, "project");

			return source;
		}

		private static bool IsDocProject(Globals globals)
		{
			return globals.get_VariableExists("IsDocProject")
				&& ((string) globals["IsDocProject"]).Equals("true", StringComparison.OrdinalIgnoreCase);
		}

		internal static Project GetVSProject(object project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			Project projectBase = project as Project;

			if (projectBase != null)
				return projectBase;

			VSProject2 vsProject2 = project as VSProject2;

			if (vsProject2 != null)
				return vsProject2.Project;

			throw new ArgumentException(Errors.InvalidProjectArgument, "project");
		}

		private static Globals CreateGlobals(object project)
		{
			try
			{
				Project projectBase = project as Project;

				if (projectBase != null)
					return projectBase.Globals;

				VSProject2 vsProject2 = project as VSProject2;

				if (vsProject2 != null)
					return vsProject2.Project.Globals;
			}
			// Occurred in testing when launching VS 2005 as a debugger-only instance attached
			// to a managed executable.
			catch (NotImplementedException) { }

			return null;
		}
		#endregion
	}
}
